﻿using System.Reflection;
using System.Windows.Input;
using ProxyViewModel.Commands;
using ProxyViewModel;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AutoViewModel.Tests {
	[TestClass]
	public class CommandsTesting {

		public class Example {
			private ICommand _testCommand;

			public ICommand TestCommand {
				get {
					if (_testCommand == null) {
						_testCommand = new ProxyCommand(this, "DoSomethingCommand");
					}
					return _testCommand;
				}
			}

			private bool _commandCanExecuteInvoked;
			private bool _commandInvoked;

			public bool CommandCanExecuteInvoked {
				get { return _commandCanExecuteInvoked; }
			}

			public bool CommandInvoked {
				get { return _commandInvoked; }
			}

			public void DoSomethingCommand() {
				_commandInvoked = true;
			}

			public bool CanExecuteDoSomethingCommand() {
				_commandCanExecuteInvoked = true;
				return true;
			}
		}

		public class AViewModel {
			private bool _commandCanExecuteInvoked;
			private bool _commandInvoked;

			public bool CommandInvoked {
				get { return _commandInvoked; }
			}

			public bool CommandCanExecuteInvoked {
				get { return _commandCanExecuteInvoked; }
			}

			public void DoSomethingCommand() {
				_commandInvoked = true;
			}

			public bool CanExecuteDoSomethingCommand() {
				_commandCanExecuteInvoked = true;
				return true;
			}
		}

		public class BViewModel {
			private bool _commandInvoked;

			public bool CommandInvoked {
				get { return _commandInvoked; }
			}

			public void DoSomethingCommand() {
				_commandInvoked = true;
			}
		}

		public class CViewModel : AViewModel {
			private bool _commandInvoked2;

			public bool CommandInvoked2 {
				get { return _commandInvoked2; }
			}

			public void DoSomething2Command() {
				_commandInvoked2 = true;
			}
		}

		public class DViewModel
		{
			public void DoSomething1Command()
			{
				
			}

			public void DoSomething2Command()
			{

			}

			public void DoSomething3Command()
			{

			}

			public void DoSomething4Command()
			{

			}
		}

		public class DViewModel<T> {
			private bool _commandCanExecuteInvoked;
			private bool _commandInvoked;
			private T _canExecuteObject;
			private T _executeObject;

			public T ExecuteObject {
				get { return _executeObject; }
			}

			public T CanExecuteObject {
				get { return _canExecuteObject; }
			}

			public bool CommandCanExecuteInvoked {
				get { return _commandCanExecuteInvoked; }
			}

			public bool CommandInvoked {
				get { return _commandInvoked; }
			}

			public void MyTestCommand(T input) {
				_executeObject = input;
				_commandInvoked = true;
			}

			public bool CanExecuteMyTestCommand(T input) {
				_canExecuteObject = input;
				_commandCanExecuteInvoked = true;
				return true;
			}
		}

		public class EViewModel<T> {
			private bool _commandInvoked;
			private T _executeObject;

			public T ExecuteObject {
				get { return _executeObject; }
			}

			public bool CommandInvoked {
				get { return _commandInvoked; }
			}

			public void MyTestCommand(T input) {
				_executeObject = input;
				_commandInvoked = true;
			}
		}

		[TestMethod]
		public void TestCommandGeneration() {
			AViewModel viewmodel = ProxyViewModelFactory.CreateProxyViewModel<AViewModel>();
			TriggerCommandCanExecuteAndExecute(viewmodel, "DoSomething", null);

			Assert.IsTrue(viewmodel.CommandInvoked);
			Assert.IsTrue(viewmodel.CommandCanExecuteInvoked);
		}

		[TestMethod]
		public void TestCommandGenerationWithoutCanExecuteMethod() {
			BViewModel viewmodel = ProxyViewModelFactory.CreateProxyViewModel<BViewModel>();
			TriggerCommandCanExecuteAndExecute(viewmodel, "DoSomething", null);
			Assert.IsTrue(viewmodel.CommandInvoked);
		}

		[TestMethod]
		public void TestCommandGenerationWithInheritance() {
			CViewModel viewmodel = ProxyViewModelFactory.CreateProxyViewModel<CViewModel>();

			TriggerCommandCanExecuteAndExecute(viewmodel, "DoSomething", null);
			TriggerCommandCanExecuteAndExecute(viewmodel, "DoSomething2", null);

			Assert.IsTrue(viewmodel.CommandInvoked);
			Assert.IsTrue(viewmodel.CommandCanExecuteInvoked);
		}

		[TestMethod]
		public void TriggerWithSpecifcObject() {
			DViewModel<decimal> viewmodel = ProxyViewModelFactory.CreateProxyViewModel<DViewModel<decimal>>();

			decimal val = 342;
			TriggerCommandCanExecuteAndExecute(viewmodel, "MyTest", val);

			Assert.IsTrue(viewmodel.CommandInvoked);
			Assert.IsTrue(viewmodel.CommandCanExecuteInvoked);
			Assert.AreEqual(val, viewmodel.ExecuteObject);
			Assert.AreEqual(val, viewmodel.CanExecuteObject);
		}

		[TestMethod]
		public void TriggerWithSpecifcObjectNoCanExecuteMethod() {
			EViewModel<decimal> viewmodel = ProxyViewModelFactory.CreateProxyViewModel<EViewModel<decimal>>();
			decimal val = 342;
			TriggerCommandCanExecuteAndExecute(viewmodel, "MyTest", val);
			Assert.IsTrue(viewmodel.CommandInvoked);
			Assert.AreEqual(val, viewmodel.ExecuteObject);
		}

		[TestMethod]
		public void TriggerWithNullObject() {
			EViewModel<object> viewmodel = ProxyViewModelFactory.CreateProxyViewModel<EViewModel<object>>();
			object val = null;
			TriggerCommandCanExecuteAndExecute(viewmodel, "MyTest", val);
			Assert.IsTrue(viewmodel.CommandInvoked);
			Assert.AreEqual(val, viewmodel.ExecuteObject);
		}

		[TestMethod]
		public void TestIProxyCommandsContainerImplementation() {
			CViewModel viewModel = ProxyViewModelFactory.CreateProxyViewModel<CViewModel>();
			Assert.IsTrue(viewModel is IProxyCommandsContainer, "CViewModel must implement IProxyCommandsContainer");
			IProxyCommandsContainer container = viewModel as IProxyCommandsContainer;
			Assert.AreEqual(2, container.Commands.Count, "CViewModel has to have two commands");
		}

		[TestMethod]
		public void TestCommandsInvalidation() {
			CViewModel viewModel = ProxyViewModelFactory.CreateProxyViewModel<CViewModel>();

			IProxyCommandsContainer container = viewModel as IProxyCommandsContainer;

			int totalPredicatesInvoked = 0;

			container.Commands.ToList().ForEach(e => e.CanExecuteChanged += (s, ev) => {
				totalPredicatesInvoked += 1;
			});

			ProxyCommandManager.InvalidateRequerySuggested(viewModel);

			Assert.AreEqual(2, totalPredicatesInvoked, "totalPredicatesInvoked must be two, because there are tow commands in CViewModel");
		}

		[TestMethod]
		public void TestMultipleCommandsInvalidation()
		{
			DViewModel viewModel = ProxyViewModelFactory.CreateProxyViewModel<DViewModel>();

			IProxyCommandsContainer container = viewModel as IProxyCommandsContainer;

			int totalPredicatesInvoked = 0;

			container.Commands.ToList().ForEach(e => e.CanExecuteChanged += (s, ev) =>
			{
				totalPredicatesInvoked += 1;
			});

			ProxyCommandManager.InvalidateRequerySuggested(viewModel);

			Assert.AreEqual(4, totalPredicatesInvoked, "totalPredicatesInvoked must be two, because there are tow commands in CViewModel");
		}

		private void TriggerCommandCanExecuteAndExecute(object viewModel, string commandName, object obj) {
			ICommand command = GetCommand(viewModel, commandName);

			command.CanExecute(obj);
			command.Execute(obj);
		}

		public static ICommand GetCommand(object viewModel, string commandName) {
			PropertyInfo commandProperty = viewModel.GetType().GetProperty(commandName);
			Assert.AreEqual(typeof(ICommand), commandProperty.PropertyType);
			return (ICommand)commandProperty.GetValue(viewModel, null);
		}
	}
}
